package com.wxsm.wechat.service;

import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.wxsm.wechat.core.jpa.JpaSelection;
import com.wxsm.wechat.dao.CommentDao;
import com.wxsm.wechat.dao.ContentDao;
import com.wxsm.wechat.dao.UpDao;
import com.wxsm.wechat.dao.UserDao;
import com.wxsm.wechat.model.Condition;
import com.wxsm.wechat.model.entity.CommentEntity;
import com.wxsm.wechat.model.entity.ContentEntity;
import com.wxsm.wechat.model.entity.UserEntity;
import com.wxsm.wechat.model.enums.ContentTypeEnum;
import com.wxsm.wechat.model.enums.IsDelEnum;
import com.wxsm.wechat.model.enums.IsShowEnum;
import com.wxsm.wechat.util.ActionUtil;
import lombok.AllArgsConstructor;
import org.hibernate.jpa.criteria.OrderImpl;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Order;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * Created with Yang Huan
 * Date: 2017/3/2
 * Time: 11:34
 */
@Service
@AllArgsConstructor
public class ContentService {

    private final ContentDao contentDao;
    private final CommentDao commentDao;
    private final UserDao userDao;
    private final UpDao upDao;
    private final MessageService messageService;
    private final SpatialContext geo = SpatialContext.GEO;
    private final Double radius = 100D;//100km内

    public Page<ContentEntity> findNewsContentPage(Pageable pageable) {
        return contentDao.findPage((root, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("type"), ContentTypeEnum.NEWS));
            list.add(cb.equal(root.get("isDel"), IsDelEnum.NOT_DELETE));
            Order[] orders = new Order[]{new OrderImpl(root.get("created"), false)};
            return JpaSelection.builder().predicates(list).selections(ContentEntity.pageSelections(root)).orders(orders).build();
        }, pageable);
    }

    @Transactional
    public ContentEntity saveNearbyContent(ContentEntity contentEntity) {
        String openId = ActionUtil.getOpenId();
        contentDao.updateNearbyContentNotShow(openId);
        contentEntity.setOpenId(openId);
        ContentEntity result = contentDao.save(contentEntity);
        messageService.attention(result.getId());
        return result;
    }

    public ContentEntity findNearbyContentById(Integer id, Double longitude, Double latitude) {
        ContentEntity contentEntity = contentDao.findOne(id);
        if (upDao.countByOpenIdAndContentId(ActionUtil.getOpenId(), id) > 0) {
            contentEntity.setIsUp(true);
        }
        UserEntity userEntity = userDao.findByOpenId(contentEntity.getOpenId());
        Boolean showHometown = userEntity.getShowHometown();
        contentEntity.setShowHometown(showHometown);
        contentEntity.setHometown(showHometown ? userEntity.getHometown() : "");
        if (null != longitude && null != latitude) {
            Point sourcePoint = geo.makePoint(longitude, latitude);
            Double distance = geo.calcDistance(sourcePoint, geo.makePoint(contentEntity.getLongitude(), contentEntity.getLatitude())) * DistanceUtils.DEG_TO_KM;
            contentEntity.setDistance(Double.valueOf(String.format("%.2f", distance)));
        }

        messageService.removeOfflineMessage(id, ActionUtil.getOpenId());
        return contentEntity;
    }

    public Page<CommentEntity> findCommentsPage(Integer contentId, Condition condition) {
        return commentDao.findPage((root, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("contentId"), contentId));
            list.add(cb.equal(root.get("isDel"), IsDelEnum.NOT_DELETE));
            if (null != condition.getLastId()) {
                list.add(cb.lt(root.get("id"), condition.getLastId()));
            }
            Order[] orders = new Order[]{new OrderImpl(root.get("id"), false)};
            return JpaSelection.builder().predicates(list).orders(orders).build();
        }, condition.getPageRequest());
    }

    @Transactional
    public void up(Integer id) {
        upDao.insert(ActionUtil.getOpenId(), id);
        contentDao.up(id);
    }

    @Transactional
    public void delete(Integer id) {
        contentDao.delete(id);
        messageService.cancelAttention(id);
    }

    public List<ContentEntity> findNearbyContent(Double longitude, Double latitude) {
        Point sourcePoint = geo.makePoint(longitude, latitude);
        Rectangle rectangle = geo.getDistCalc().calcBoxByDistFromPt(sourcePoint, radius * DistanceUtils.KM_TO_DEG, geo, null);
        List<ContentEntity> contentList = contentDao.findAll((root, cb) -> {
            List<Predicate> list = new ArrayList<>();
            list.add(cb.equal(root.get("isDel"), IsDelEnum.NOT_DELETE));
            list.add(cb.equal(root.get("isShow"), IsShowEnum.SHOW));
            list.add(cb.equal(root.get("type"), ContentTypeEnum.NEARBY));
            list.add(cb.between(root.get("longitude"), rectangle.getMinX(), rectangle.getMaxX()));
            list.add(cb.between(root.get("latitude"), rectangle.getMinY(), rectangle.getMaxY()));
            return JpaSelection.builder().predicates(list).selections(ContentEntity.pageSelections(root)).build();
        });
        List<Integer> contentIds = new ArrayList<>();
        contentList.forEach(contentEntity -> {
            Double distance = geo.calcDistance(sourcePoint, geo.makePoint(contentEntity.getLongitude(), contentEntity.getLatitude())) * DistanceUtils.DEG_TO_KM;
            contentEntity.setDistance(Double.valueOf(String.format("%.2f", distance)));
            contentIds.add(contentEntity.getId());
        });
        if (!StringUtils.isEmpty(ActionUtil.getOpenId()) && contentIds.size() > 0) {
            List<Integer> upContentIds = upDao.selectUpContentIds(ActionUtil.getOpenId(), contentIds);
            contentList.forEach(contentEntity -> {
                if (upContentIds.contains(contentEntity.getId())) {
                    contentEntity.setIsUp(true);
                }
            });
        }
        contentList.sort(Comparator.comparing(ContentEntity::getDistance));
        return contentList;
    }
}
